#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/xpm.h>
#include <X11/extensions/shape.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "screen.xpm"

/*
 * Constants
 */

#define AUTHOR "Eduardo Nunes Pereira <eduardonunesp@gmail.com>"
#define APPLICATION "wmk"
#define VERSION "0.1"
#define BUILD_REV ""
#define BUILD_DATE ""

#define TOOLTIP_SUPPORT 1
#define TOOLTIP_FONT "-*-helvetica-bold-r-normal-*-12-*-*-*-*-*-*-*"
#define TOOLTIP_OUTSIDE 0
#define TOOLTIP_SHOW_DELAY 750
#define TOOLTIP_RESHOW_DELAY 1500

#define TOOLTIP_SPACE 12
#define TOOLTIP_TOP	0
#define TOOLTIP_BOTTOM	1
#define TOOLTIP_LEFT	0
#define TOOLTIP_RIGHT	2

/*
 * Prototypes
 */

int isVerbose();

void initTime();
long currentTimeMillis();

Pixel getWhitePixel ();
Pixel getBlackPixel ();
int getDefaultScreen ();
int getDefaultDepth ();
void initWindow (int nArgc, char** szArgv);
void destroyWindow ();
GC getWindowGraphics ();
GC getMainGraphics ();
Display* getDisplay();

void initDisplay(char* szDisplay);
void initWindow (int argc, char* argv[]);
void initWindowMask (char* szInstallDir, char* szButtonTheme);

void loop (long nTooltipShowDelay, long nTooltipReshowDelay);

/*
 * Main
 */

int main (int argc, char* argv[]) {
	char* szTheme= NULL;
	char* szInstallDir= NULL;
	long nTooltipShowDelay= TOOLTIP_SHOW_DELAY;
	long nTooltipReshowDelay= TOOLTIP_RESHOW_DELAY;

    initTime();
    initDisplay(NULL);
    initWindow(argc, argv);
	initWindowMask(szInstallDir, szTheme);
	loop(nTooltipShowDelay, nTooltipReshowDelay);

	return 0;
}

/*
 * Usage
 */

#define USAGE \
	"usage: %s [options]\n\n"

/*
 * Functions
 */

static int _bVerbose = 1;
int isVerbose() {
    return _bVerbose;
}

static struct timeval _tStart;
void initTime() {
    gettimeofday(&_tStart, NULL);
}

long currentTimeMillis () {
	struct timeval tNow;
	struct timeval tElapsed;

	gettimeofday(&tNow, NULL);

	if (_tStart.tv_usec > tNow.tv_usec) {
		tNow.tv_usec+= 1000000;
		tNow.tv_sec--;
	}
	tElapsed.tv_sec= tNow.tv_sec - _tStart.tv_sec;
	tElapsed.tv_usec= tNow.tv_usec - _tStart.tv_usec;
	return (tElapsed.tv_sec * 1000) + (tElapsed.tv_usec / 1000);
}

static Display* _display;

Display* getDisplay () {
	return _display;
}

void initDisplay(char* szDisplay) {
	if (szDisplay == NULL && ((char*) getenv("DISPLAY")) == NULL) {
		szDisplay= ":0.0";
	}

	if (isVerbose()) {
		char* szRealDisplay= (szDisplay == NULL) ? (char*) getenv("DISPLAY") : szDisplay;
		if (szRealDisplay == NULL) {
			szRealDisplay= "localhost:0.0";
		}
		fprintf(stdout, "[%8ld] initializing display '%s'\n", currentTimeMillis(), szRealDisplay);
	}

	_display= XOpenDisplay(szDisplay);
	if (_display == NULL) {
		fprintf(
			stderr, 
			"%s: couldn't open display '%s'.\n",
            APPLICATION,
			(szDisplay == NULL) ? ((char*) getenv("DISPLAY")) : szDisplay
		);
		exit(-1);
	}
}

static int _nDefaultScreen, _nDefaultDepth;
static Window _wRoot, _wMain, _wIcon;
static GC _gcMain, _gcWindow;
static XpmAttributes _attrButtonTheme;
static Pixmap _pButtonTheme, _pButtonThemeMask;
static XpmAttributes _attrWindow;
static Pixmap _pWindow, _pWindowMask;
static Pixel _pWhite, _pBlack;

Pixel getWhitePixel () {
	return _pWhite;
}

Pixel getBlackPixel () {
	return _pBlack;
}

int getDefaultScreen () {
	return _nDefaultScreen;
}

int getDefaultDepth () {
	return _nDefaultDepth;
}

Window getRootWindow () {
	return _wRoot;
}

Window getMainWindow () {
	return _wMain;
}

Window getIconWindow () {
	return _wIcon;
}

GC getMainGraphics () {
	return _gcMain;
}

GC getWindowGraphics () {
	return _gcWindow;
}

void initWindow(int argc, char* argv[]) {
	char* szApplicationName = APPLICATION;
	Display* display= getDisplay();
	XSizeHints *xsizehints;
	XWMHints* xwmhints;
	XClassHint* xclasshint;
	XTextProperty* xtApplication;
	XGCValues xgcMain;
	
	if (isVerbose()) {
		fprintf(stdout, "[%8ld] initializing application window\n", currentTimeMillis());
	}

	_nDefaultScreen= DefaultScreen(display);
	_nDefaultDepth= DefaultDepth(display, _nDefaultScreen);
	_wRoot= RootWindow(display, _nDefaultScreen);

	XSelectInput(display, _wRoot, PropertyChangeMask);
	
	_pWhite= WhitePixel(display, _nDefaultScreen);
	_pBlack= BlackPixel(display, _nDefaultScreen);

	xsizehints= XAllocSizeHints();
	xsizehints->flags= USSize | USPosition;
	xsizehints->width= xsizehints->height= 64;
	
	_wMain= XCreateSimpleWindow(display, _wRoot, 0, 0, 64, 64, 5, _pWhite, _pBlack);
	if (_wMain == 0) {
		fprintf(stderr, "Cannot create main window.\n");
		exit(-1);
	}
	
	_wIcon= XCreateSimpleWindow(display, _wMain, 0, 0, 64, 64, 5, _pWhite, _pBlack);
	if (_wIcon == 0) {
		fprintf(stderr, "Cannot create icon window.\n");
		exit(-1);
	}
	
	xwmhints= XAllocWMHints();
	xwmhints->flags= WindowGroupHint | IconWindowHint | StateHint;    
 	xwmhints->icon_window= _wIcon;
	xwmhints->window_group= _wMain;
	xwmhints->initial_state= WithdrawnState;
	XSetWMHints(display, _wMain, xwmhints);
	
	xclasshint= XAllocClassHint();
	xclasshint->res_name= APPLICATION;
	xclasshint->res_class= APPLICATION;
	XSetClassHint(display, _wMain, xclasshint);

	XSetWMNormalHints(display, _wMain, xsizehints);

	xtApplication= (XTextProperty*) malloc(sizeof(XTextProperty));
	if (XStringListToTextProperty(&szApplicationName, 1, xtApplication) == 0) {
		fprintf(stderr, "Cannot set window title.\n");
		exit(-1);
	}
	XSetWMName(display, _wMain, xtApplication);
	
	_gcMain= XCreateGC(display, _wMain, (GCForeground | GCBackground), &xgcMain);
	if (_gcMain == NULL) {
		fprintf(stderr, "Cannot create graphics context.\n");
		exit(-1);
	}

	XSelectInput(display, _wMain, ExposureMask | ButtonPressMask | PointerMotionMask | StructureNotifyMask | LeaveWindowMask);
	XSelectInput(display, _wIcon, ExposureMask | ButtonPressMask | PointerMotionMask | StructureNotifyMask | LeaveWindowMask);
	
	XSetCommand(display, _wMain, argv, argc);
	
	XMapWindow(display, _wMain);
}

void initWindowMask (char* szInstallDir, char* szButtonTheme) {
	Display* display= getDisplay();
	GC gc;
	Window wRoot= getRootWindow();
	Window wMain= getMainWindow();
	Window wIcon= getIconWindow();
	XGCValues xgc, xgcWindow;
	Pixmap pOpaque, pTransparent, pMask;
	char* mask= (char*) malloc(512);
	int i;

	if (isVerbose()) {
		fprintf(stdout, "[%8ld] initializing window mask\n", currentTimeMillis());
	}
	for (i= 0; i < 512; i++) {
		mask[i]= 0x00;
	}
	pTransparent= XCreateBitmapFromData(display, wRoot, mask, 64, 64);
	if (pTransparent == 0) {
		fprintf(stderr, "%s: couldn't create window mask (transparent).\n", APPLICATION);
		exit(-1);
	}
	pMask= XCreateBitmapFromData(display, wRoot, mask, 64, 64);
	if (pMask == 0) {
		fprintf(stderr, "%s: couldn't create window mask (mask buffer).\n", APPLICATION);
		exit(-1);
	}

	for (i= 0; i < 512; i++) {
		mask[i]= 0xff;
	}
	pOpaque= XCreateBitmapFromData(display, wRoot, mask, 64, 64);
	if (pOpaque == 0) {
		fprintf(stderr, "%s: couldn't create window mask (opaque).\n", APPLICATION);
		exit(-1);
	}

	gc= XCreateGC(display, pMask, (GCForeground | GCBackground), &xgc);
	if (gc == NULL) {
		fprintf(stderr, "%s: couldn't create window mask (mask graphics).\n", APPLICATION);
		exit(-1);
	}
	
	XFreePixmap(display, pOpaque);
	XFreePixmap(display, pTransparent);
	XFreeGC(display, gc);

	XShapeCombineMask(display, wMain, ShapeBounding, 0, 0, pMask, ShapeSet);
	XShapeCombineMask(display, wIcon, ShapeBounding, 0, 0, pMask, ShapeSet);

	if (isVerbose()) {
		fprintf(stdout, "[%8ld] initializing button theme '%s'\n", currentTimeMillis(), 
			szButtonTheme == NULL ? "<built-in>" : szButtonTheme);
	}

	_attrButtonTheme.valuemask|= (XpmReturnPixels | XpmReturnExtensions);
	if (szButtonTheme == NULL) {
	
    } else {
		int bCheckAgain= 0;
		struct stat buf;
		/* check for absolute button theme pathname */
		if (stat(szButtonTheme, &buf) == -1) {
			char* szNewTheme= (char*) malloc(strlen(szButtonTheme) + 4);
			strcpy(szNewTheme, szButtonTheme);
			strcat(szNewTheme, ".xpm");
			if (isVerbose()) {
				fprintf(stdout, "[%8ld] theme file '%s' not found, trying '%s'\n", currentTimeMillis(), szButtonTheme, szNewTheme);
			}
			/* check for absolute button theme pathname (with .xpm added) */
			if (stat(szNewTheme, &buf) == 0) {
				szButtonTheme= szNewTheme;
				if (isVerbose()) {
					fprintf(stdout, "[%8ld] initializing button theme '%s'\n", currentTimeMillis(), szButtonTheme);
				}
			} else {
				bCheckAgain= 1;
				free(szNewTheme);
			}
		}
		if (bCheckAgain && szInstallDir != NULL) {
			char* szNewTheme= (char*) malloc(strlen(szInstallDir) + strlen(szButtonTheme) + 5);
			strcpy(szNewTheme, szInstallDir);
			if (szNewTheme[strlen(szNewTheme) - 1] != '/') {
				strcat(szNewTheme, "/");
			}
			strcat(szNewTheme, szButtonTheme);
			if (stat(szNewTheme, &buf) == 0) {
				bCheckAgain= 0;
				szButtonTheme= szNewTheme;
				if (isVerbose()) {
					fprintf(stdout, "[%8ld] initializing button theme '%s'\n", currentTimeMillis(), szButtonTheme);
				}
			} else {
				strcat(szNewTheme, ".xpm");
				if (stat(szNewTheme, &buf) == 0) {
					bCheckAgain= 0;
					szButtonTheme= szNewTheme;
					if (isVerbose()) {
						fprintf(stdout, "[%8ld] initializing button theme '%s'\n", currentTimeMillis(), szButtonTheme);
					}
				}
			}
		}
		if (bCheckAgain) {
			/* as a goody check the ~/.wmpager directory if it exists */
			char* szHome= (char*) getenv("HOME");
			if (szHome) {
				/* one really shouldn't copy&paste but hey this is a q&d tool */
			}
		}
		if (
			XpmReadFileToPixmap(
				display, wRoot, szButtonTheme, &_pButtonTheme, &_pButtonThemeMask, &_attrButtonTheme
			) != XpmSuccess
		) {
			fprintf(stderr, "%s: couldn't read button theme '%s'.\n", APPLICATION, szButtonTheme);
			exit(-1);
		}
	}

	if (isVerbose()) {
		fprintf(stdout, "[%8ld] initializing screen buffer\n", currentTimeMillis());
	}

	_attrWindow.valuemask|= (XpmReturnPixels | XpmReturnExtensions);
	if (
		XpmCreatePixmapFromData(
			display, wRoot, screen_xpm, &_pWindow, &_pWindowMask, &_attrWindow
		) != XpmSuccess
	) {
		fprintf(stderr, "%s: couldn't create screen buffer.\n", APPLICATION);
		exit(-1);
	}



	_gcWindow= XCreateGC(_display, _pWindow, (GCForeground | GCBackground), &xgcWindow);
	if (gc == NULL) {
		fprintf(stderr, "%s: couldn't create screen buffer graphics.\n", APPLICATION);
		exit(-1);
	}
}

void loop (long nTooltipShowDelay, long nTooltipReshowDelay) {
	Display* display= getDisplay();
	XEvent event;
	long nTooltipTimer= -1, nTooltipHideTimer= -1, nNow;

	if (isVerbose()) {
		fprintf(stdout, "[%8ld] starting event loop\n", currentTimeMillis());
	}
	for (;;) {
		while (XPending(display) || (nTooltipTimer == -1)) {
			XNextEvent(display, &event);
			switch (event.type) {
			}
		}
		usleep(50000);
		nNow= currentTimeMillis();
		if (
			nTooltipTimer != -1 && 
			(
				(nNow > nTooltipTimer + nTooltipShowDelay) ||
				(nNow < nTooltipHideTimer + nTooltipReshowDelay)
			)
		)	{
			nTooltipTimer= -1;
		}
	}
}
